#pragma once

#include <folly/ProducerConsumerQueue.h>
#include <folly/String.h>
#include <grpcpp/grpcpp.h>
#include <stdint.h>

#include "common/BlockingQueue.h"
#include "common/Network.h"
#include "common/Pipeline.h"
#include "common/TopologyUpdate.h"
#include "nexus/proto/nexus.grpc.pb.h"

namespace nexus {

class Server {
 public:
  Server(
      uint16_t port,
      Pipeline& pipeline,
      BlockingQueue<TopologyUpdate>& topologyUpdatesQueue,
      std::map<std::string, Network>& networks);
  ~Server();

  void run();
  void stop();

  friend class ServiceImpl;

 private:
  uint16_t port_;
  std::unique_ptr<grpc::Server> server_;
  Pipeline& pipeline_;
  BlockingQueue<TopologyUpdate>& topologyUpdatesQueue_;
  std::map<std::string, Network>& networks_;
};

class ServiceImpl final : public proto::NexusService::Service {
 public:
  ServiceImpl(Server* server) : server_(server) {}

  grpc::Status CreateNetwork(
      grpc::ServerContext* ctx,
      const proto::Network* network,
      proto::Response* resp);

  grpc::Status AddDevice(
      grpc::ServerContext* ctx,
      const proto::Device* device,
      proto::Response* resp);

  grpc::Status AddLink(
      grpc::ServerContext* ctx, const proto::Link* link, proto::Response* resp);

  grpc::Status AddRoute(
      grpc::ServerContext* ctx,
      const proto::Route* route,
      proto::Response* resp);

  grpc::Status AddRoutes(
      grpc::ServerContext* ctx,
      const proto::Routes* routes,
      proto::Response* resp);

  grpc::Status GetNetwork(
      grpc::ServerContext* ctx,
      const proto::GetNetworkRequest* req,
      proto::Network* resp);

  grpc::Status GetNetworks(
      grpc::ServerContext* ctx,
      const proto::EmptyRequest* req,
      proto::Networks* resp);

  grpc::Status GetPath(
      grpc::ServerContext* ctx,
      const proto::GetPathRequest* req,
      proto::GetPathResponse* resp);

 private:
  Server* server_;
};

} // namespace nexus